#include "AudioIO.h"
#include "../CommonCommandFlags.h"
#include "DeviceManager.h"
#include "LabelTrack.h"
#include "Prefs.h"
#include "Project.h"
#include "ProjectAudioIO.h"
#include "../ProjectAudioManager.h"
#include "ProjectHistory.h"
#include "../ProjectWindows.h"
#include "../SelectUtilities.h"
#include "../SoundActivatedRecord.h"
#include "TrackFocus.h"
#include "../TrackPanel.h"
#include "../TransportUtilities.h"
#include "UndoManager.h"
#include "../prefs/RecordingPrefs.h"
#include "../prefs/TracksPrefs.h"
#include "WaveTrack.h"
#include "ViewInfo.h"
#include "Viewport.h"
#include "CommandContext.h"
#include "../toolbars/ControlToolBar.h"
#include "../toolbars/ToolManager.h"
#include "AudacityMessageBox.h"
#include "BasicUI.h"
#include "ProgressDialog.h"

#include <thread>
#include <float.h>
#include <wx/app.h>

// private helper classes and functions
namespace {
// TODO: Should all these functions which involve
// the toolbar actually move into ControlToolBar?

/// MakeReadyToPlay stops whatever is currently playing
/// and pops the play button up.  Then, if nothing is now
/// playing, it pushes the play button down and enables
/// the stop button.
bool MakeReadyToPlay(AudacityProject& project)
{
    auto& toolbar = ControlToolBar::Get(project);
    wxCommandEvent evt;

    // If this project is playing, stop playing
    auto gAudioIO = AudioIOBase::Get();
    if (gAudioIO->IsStreamActive(
            ProjectAudioIO::Get(project).GetAudioIOToken()
            )) {
        // Make momentary changes of button appearances
        toolbar.SetPlay(false);      //Pops
        toolbar.SetStop();       //Pushes stop down
        toolbar.OnStop(evt);

        using namespace std::chrono;
        std::this_thread::sleep_for(100ms);
    }

    // If it didn't stop playing quickly, or if some other
    // project is playing, return
    if (gAudioIO->IsBusy()) {
        return false;
    }

    return true;
}

void DoMoveToLabel(AudacityProject& project, bool next)
{
    auto& tracks = TrackList::Get(project);
    auto& trackFocus = TrackFocus::Get(project);
    auto& viewport = Viewport::Get(project);
    auto& projectAudioManager = ProjectAudioManager::Get(project);

    // Find the number of label tracks, and ptr to last track found
    auto trackRange = tracks.Any<LabelTrack>();
    auto lt = *trackRange.rbegin();
    auto nLabelTrack = trackRange.size();

    if (nLabelTrack == 0) {
        trackFocus.MessageForScreenReader(XO("no label track"));
    } else if (nLabelTrack > 1) {
        // find first label track, if any, starting at the focused track
        lt
            =*tracks.Find(trackFocus.Get()).Filter<LabelTrack>();
        if (!lt) {
            trackFocus.MessageForScreenReader(
                XO("no label track at or below focused track"));
        }
    }

    // If there is a single label track, or there is a label track at or below
    // the focused track
    auto& selectedRegion = ViewInfo::Get(project).selectedRegion;
    if (lt) {
        int i;
        if (next) {
            i = lt->FindNextLabel(selectedRegion);
        } else {
            i = lt->FindPrevLabel(selectedRegion);
        }

        if (i >= 0) {
            const LabelStruct* label = lt->GetLabel(i);
            bool newDefault = projectAudioManager.Looping();
            if (ProjectAudioIO::Get(project).IsAudioActive()) {
                TransportUtilities::DoStopPlaying(project);
                selectedRegion = label->selectedRegion;
                viewport.Redraw();
                TransportUtilities::DoStartPlaying(project, newDefault);
            } else {
                selectedRegion = label->selectedRegion;
                viewport.ScrollIntoView(selectedRegion.t0());
                viewport.Redraw();
            }
            /* i18n-hint:
               String is replaced by the name of a label,
               first number gives the position of that label in a sequence
               of labels,
               and the last number is the total number of labels in the sequence.
            */
            auto message = XO("%s %d of %d")
                           .Format(label->title, i + 1, lt->GetNumLabels());
            trackFocus.MessageForScreenReader(message);
        } else {
            trackFocus.MessageForScreenReader(XO("no labels in label track"));
        }
    }
}

bool IsLoopingEnabled(const AudacityProject& project)
{
    auto& playRegion = ViewInfo::Get(project).playRegion;
    return playRegion.Active();
}
}

// Strings for menu items and also for dialog titles
/* i18n-hint Sets a starting point for looping play */
static const auto SetLoopInTitle = XXO("Set Loop &In");
/* i18n-hint Sets an ending point for looping play */
static const auto SetLoopOutTitle = XXO("Set Loop &Out");

// Menu handler functions

namespace {
// This plays (once, with fixed bounds) OR Stops audio.  It's a toggle.
// The default binding for Shift+SPACE.
void OnPlayOnceOrStop(const CommandContext& context)
{
    if (TransportUtilities::DoStopPlaying(context.project)) {
        return;
    }
    TransportUtilities::DoStartPlaying(context.project);
}

void OnPlayStopSelect(const CommandContext& context)
{
    ProjectAudioManager::Get(context.project).DoPlayStopSelect();
}

// This plays (looping, maybe adjusting the loop) OR Stops audio.  It's a toggle.
// The default binding for SPACE
void OnPlayDefaultOrStop(const CommandContext& context)
{
    auto& project = context.project;
    if (TransportUtilities::DoStopPlaying(project)) {
        return;
    }

    if (!MakeReadyToPlay(project)) {
        return;
    }

    // Now play in a loop
    // Will automatically set mLastPlayMode
    TransportUtilities::PlayCurrentRegionAndWait(context, true);
}

void OnPause(const CommandContext& context)
{
    ProjectAudioManager::Get(context.project).OnPause();
}

void OnRecord(const CommandContext& context)
{
    TransportUtilities::RecordAndWait(context, false);
}

// If first choice is record same track 2nd choice is record NEW track
// and vice versa.
void OnRecord2ndChoice(const CommandContext& context)
{
    TransportUtilities::RecordAndWait(context, true);
}

void OnPunchAndRoll(const CommandContext& context)
{
    AudacityProject& project = context.project;
    auto& viewInfo = ViewInfo::Get(project);

    static const auto url
        =wxT("Punch_and_Roll_Record#Using_Punch_and_Roll_Record");

    auto gAudioIO = AudioIO::Get();
    if (gAudioIO->IsBusy()) {
        return;
    }

    // Ignore all but left edge of the selection.
    viewInfo.selectedRegion.collapseToT0();
    double t1 = std::max(0.0, viewInfo.selectedRegion.t1());

    // Checking the selected tracks: making sure they all have the same rate
    const auto selectedTracks{ GetPropertiesOfSelected(project) };
    const int rateOfSelected{ selectedTracks.rateOfSelected };
    const bool allSameRate{ selectedTracks.allSameRate };

    if (!allSameRate) {
        AudacityMessageBox(XO("The tracks selected "
                              "for recording must all have the same sampling rate"),
                           XO("Mismatched Sampling Rates"),
                           wxICON_ERROR | wxCENTRE);

        return;
    }

    // Decide which tracks to record in.
    auto tracks
        =ProjectAudioManager::ChooseExistingRecordingTracks(project, true, rateOfSelected);
    if (tracks.empty()) {
        auto recordingChannels
            =std::max(0, AudioIORecordChannels.Read());
        auto message
            =(recordingChannels == 1)
              ? XO("Please select in a mono track.")
              : (recordingChannels == 2)
              ? XO("Please select in a stereo track or two mono tracks.")
              : XO("Please select at least %d channels.").Format(recordingChannels);
        BasicUI::ShowErrorDialog(*ProjectFramePlacement(&project),
                                 XO("Error"), message, url);
        return;
    }

    // Delete the portion of the target tracks right of the selection, but first,
    // remember a part of the deletion for crossfading with the new recording.
    // We may also adjust the starting point leftward if it is too close to the
    // end of the track, so that at least some nonzero crossfade data can be
    // taken.
    PRCrossfadeData crossfadeData;
    const double crossFadeDuration = std::max(0.0,
                                              gPrefs->Read(AUDIO_ROLL_CROSSFADE_KEY, DEFAULT_ROLL_CROSSFADE_MS)
                                              / 1000.0
                                              );

    // The test for t1 == 0.0 stops punch and roll deleting everything where the
    // selection is at zero.  There wouldn't be any cued audio to play in
    // that case, so a normal record, not a punch and roll, is called for.
    bool error = (t1 == 0.0);

    double newt1 = t1;
    for (const auto& wt : tracks) {
        auto rate = wt->GetRate();
        sampleCount testSample(floor(t1 * rate));
        const auto& intervals = as_const(*wt).Intervals();
        auto pred = [rate](sampleCount testSample){
            return
                [rate, testSample](const auto& pInterval){
                auto start = floor(pInterval->Start() * rate + 0.5);
                auto end = floor(pInterval->End() * rate + 0.5);
                auto ts = testSample.as_double();
                return ts >= start && ts < end;
            };
        };
        auto begin = intervals.begin(), end = intervals.end(),
             iter = std::find_if(begin, end, pred(testSample));
        if (iter == end) {
            // Bug 1890 (an enhancement request)
            // Try again, a little to the left.
            // Subtract 10 to allow a selection exactly at or slightly after the
            // end time
            iter = std::find_if(begin, end, pred(testSample - 10));
        }
        if (iter == end) {
            error = true;
        } else {
            // May adjust t1 left
            // Let's ignore the possibility of a clip even shorter than the
            // crossfade duration!
            newt1 = std::min(newt1, (*iter).get()->End() - crossFadeDuration);
        }
    }

    if (error) {
        auto message = XO("Please select a time within a clip.");
        BasicUI::ShowErrorDialog(
            *ProjectFramePlacement(&project), XO("Error"), message, url);
        return;
    }

    t1 = newt1;
    for (const auto& wt : tracks) {
        const auto endTime = wt->GetEndTime();
        const auto duration
            =std::max(0.0, std::min(crossFadeDuration, endTime - t1));
        const size_t getLen = floor(duration * wt->GetRate());
        if (getLen > 0) {
            // TODO more-than-two-channels
            const auto nChannels = std::min<size_t>(2, wt->NChannels());
            crossfadeData.resize(nChannels);
            float* buffers[2]{};
            for (size_t ii = 0; ii < nChannels; ++ii) {
                auto& data = crossfadeData[ii];
                data.resize(getLen);
                buffers[ii] = data.data();
            }
            const sampleCount pos = wt->TimeToLongSamples(t1);
            if (!wt->GetFloats(0, nChannels, buffers, pos, getLen)) {
                // TODO error message
                return;
            }
        }
    }

    // Change tracks only after passing the error checks above
    for (const auto& wt : tracks) {
        wt->Clear(t1, wt->GetEndTime());
    }

    // Choose the tracks for playback.
    TransportSequences transportTracks;
    const auto duplex = ProjectAudioManager::UseDuplex();
    if (duplex) {
        // play all
        transportTracks = MakeTransportTracks(
            TrackList::Get(project), false, true);
    } else {
        // play recording tracks only
        for (auto& pTrack : tracks) {
            transportTracks.playbackSequences.push_back(pTrack);
        }
    }

    // Unlike with the usual recording, a track may be chosen both for playback
    // and recording.
    std::copy(tracks.begin(), tracks.end(),
              back_inserter(transportTracks.captureSequences));

    // Try to start recording
    auto options = ProjectAudioIO::GetDefaultOptions(project);
    options.rate = rateOfSelected;
    options.preRoll = std::max(0.0,
                               gPrefs->Read(AUDIO_PRE_ROLL_KEY, DEFAULT_PRE_ROLL_SECONDS));
    options.pCrossfadeData = &crossfadeData;
    bool success = ProjectAudioManager::Get(project).DoRecord(project,
                                                              transportTracks,
                                                              t1, DBL_MAX,
                                                              false, // altAppearance
                                                              options);

    if (success) {
        // Undo state will get pushed elsewhere, when record finishes
    } else {
        // Roll back the deletions
        ProjectHistory::Get(project).RollbackState();
    }
}

void OnTogglePlayRegion(const CommandContext& context)
{
    SelectUtilities::TogglePlayRegion(context.project);
}

void OnClearPlayRegion(const CommandContext& context)
{
    SelectUtilities::ClearPlayRegion(context.project);
}

void OnSetPlayRegionIn(const CommandContext& context)
{
    auto& project = context.project;
    auto& playRegion = ViewInfo::Get(project).playRegion;
    if (!playRegion.Active()) {
        SelectUtilities::ActivatePlayRegion(project);
    }
    SelectUtilities::OnSetRegion(project,
                                 true, false, SetLoopInTitle.Stripped());
}

void OnSetPlayRegionOut(const CommandContext& context)
{
    auto& project = context.project;
    auto& playRegion = ViewInfo::Get(project).playRegion;
    if (!playRegion.Active()) {
        SelectUtilities::ActivatePlayRegion(project);
    }
    SelectUtilities::OnSetRegion(project,
                                 false, false, SetLoopOutTitle.Stripped());
}

void OnSetPlayRegionToSelection(const CommandContext& context)
{
    SelectUtilities::SetPlayRegionToSelection(context.project);
}

void OnRescanDevices(const CommandContext& WXUNUSED(context))
{
    DeviceManager::Instance()->Rescan();
}

void OnSoundActivated(const CommandContext& context)
{
    AudacityProject& project = context.project;

    SoundActivatedRecordDialog dialog(&GetProjectFrame(project) /* parent */);
    dialog.ShowModal();
}

void OnToggleSoundActivated(const CommandContext& WXUNUSED(context))
{
    SoundActivatedRecord.Toggle();
    gPrefs->Flush();
    ToolManager::ModifyAllProjectToolbarMenus();
}

void OnTogglePlayRecording(const CommandContext& WXUNUSED(context))
{
    bool Duplex;
    gPrefs->Read(wxT("/AudioIO/Duplex"), &Duplex, true);
    gPrefs->Write(wxT("/AudioIO/Duplex"), !Duplex);
    gPrefs->Flush();
    ToolManager::ModifyAllProjectToolbarMenus();
}

void OnToggleSWPlaythrough(const CommandContext& WXUNUSED(context))
{
    bool SWPlaythrough;
    gPrefs->Read(wxT("/AudioIO/SWPlaythrough"), &SWPlaythrough, false);
    gPrefs->Write(wxT("/AudioIO/SWPlaythrough"), !SWPlaythrough);
    gPrefs->Flush();
    ToolManager::ModifyAllProjectToolbarMenus();
}

void OnStop(const CommandContext& context)
{
    ProjectAudioManager::Get(context.project).Stop();
}

void OnPlayOneSecond(const CommandContext& context)
{
    auto& project = context.project;
    if (!MakeReadyToPlay(project)) {
        return;
    }

    auto& trackPanel = TrackPanel::Get(project);
    auto options = ProjectAudioIO::GetDefaultOptions(project);

    double pos = trackPanel.GetMostRecentXPos();
    TransportUtilities::PlayPlayRegionAndWait(
        context, SelectedRegion(pos - 0.5, pos + 0.5),
        options, PlayMode::oneSecondPlay);
}

/// The idea for this function (and first implementation)
/// was from Juhana Sadeharju.  The function plays the
/// sound between the current mouse position and the
/// nearest selection boundary.  This gives four possible
/// play regions depending on where the current mouse
/// position is relative to the left and right boundaries
/// of the selection region.
void OnPlayToSelection(const CommandContext& context)
{
    auto& project = context.project;

    if (!MakeReadyToPlay(project)) {
        return;
    }

    auto& trackPanel = TrackPanel::Get(project);
    auto& viewInfo = ViewInfo::Get(project);
    const auto& selectedRegion = viewInfo.selectedRegion;

    double pos = trackPanel.GetMostRecentXPos();

    double t0, t1;
    // check region between pointer and the nearest selection edge
    if (fabs(pos - selectedRegion.t0())
        < fabs(pos - selectedRegion.t1())) {
        t0 = t1 = selectedRegion.t0();
    } else {
        t0 = t1 = selectedRegion.t1();
    }
    if (pos < t1) {
        t0=pos;
    } else {
        t1=pos;
    }

    // JKC: oneSecondPlay mode disables auto scrolling
    // On balance I think we should always do this in this function
    // since you are typically interested in the sound EXACTLY
    // where the cursor is.
    // TODO: have 'playing attributes' such as 'with_autoscroll'
    // rather than modes, since that's how we're now using the modes.

    // An alternative, commented out below, is to disable autoscroll
    // only when playing a short region, less than or equal to a second.
//   mLastPlayMode = ((t1-t0) > 1.0) ? normalPlay : oneSecondPlay;

    auto playOptions = ProjectAudioIO::GetDefaultOptions(project);

    TransportUtilities::PlayPlayRegionAndWait(
        context, SelectedRegion(t0, t1),
        playOptions, PlayMode::oneSecondPlay);
}

// The next 4 functions provide a limited version of the
// functionality of OnPlayToSelection() for keyboard users

void OnPlayBeforeSelectionStart(const CommandContext& context)
{
    auto& project = context.project;

    if (!MakeReadyToPlay(project)) {
        return;
    }

    auto& viewInfo = ViewInfo::Get(project);
    const auto& selectedRegion = viewInfo.selectedRegion;

    double t0 = selectedRegion.t0();
    double beforeLen;
    gPrefs->Read(wxT("/AudioIO/CutPreviewBeforeLen"), &beforeLen, 2.0);

    auto playOptions = ProjectAudioIO::GetDefaultOptions(project);

    TransportUtilities::PlayPlayRegionAndWait(
        context, SelectedRegion(t0 - beforeLen, t0),
        playOptions, PlayMode::oneSecondPlay);
}

void OnPlayAfterSelectionStart(const CommandContext& context)
{
    auto& project = context.project;

    if (!MakeReadyToPlay(project)) {
        return;
    }

    auto& viewInfo = ViewInfo::Get(project);
    const auto& selectedRegion = viewInfo.selectedRegion;

    double t0 = selectedRegion.t0();
    double t1 = selectedRegion.t1();
    double afterLen;
    gPrefs->Read(wxT("/AudioIO/CutPreviewAfterLen"), &afterLen, 1.0);

    auto playOptions = ProjectAudioIO::GetDefaultOptions(project);

    if (t1 - t0 > 0.0 && t1 - t0 < afterLen) {
        TransportUtilities::PlayPlayRegionAndWait(
            context, SelectedRegion(t0, t1),
            playOptions, PlayMode::oneSecondPlay);
    } else {
        TransportUtilities::PlayPlayRegionAndWait(
            context, SelectedRegion(t0, t0 + afterLen),
            playOptions, PlayMode::oneSecondPlay);
    }
}

void OnPlayBeforeSelectionEnd(const CommandContext& context)
{
    auto& project = context.project;

    if (!MakeReadyToPlay(project)) {
        return;
    }

    auto& viewInfo = ViewInfo::Get(project);
    const auto& selectedRegion = viewInfo.selectedRegion;

    double t0 = selectedRegion.t0();
    double t1 = selectedRegion.t1();
    double beforeLen;
    gPrefs->Read(wxT("/AudioIO/CutPreviewBeforeLen"), &beforeLen, 2.0);

    auto playOptions = ProjectAudioIO::GetDefaultOptions(project);

    if (t1 - t0 > 0.0 && t1 - t0 < beforeLen) {
        TransportUtilities::PlayPlayRegionAndWait(
            context, SelectedRegion(t0, t1),
            playOptions, PlayMode::oneSecondPlay);
    } else {
        TransportUtilities::PlayPlayRegionAndWait(
            context, SelectedRegion(t1 - beforeLen, t1),
            playOptions, PlayMode::oneSecondPlay);
    }
}

void OnPlayAfterSelectionEnd(const CommandContext& context)
{
    auto& project = context.project;

    if (!MakeReadyToPlay(project)) {
        return;
    }

    auto& viewInfo = ViewInfo::Get(project);
    const auto& selectedRegion = viewInfo.selectedRegion;

    double t1 = selectedRegion.t1();
    double afterLen;
    gPrefs->Read(wxT("/AudioIO/CutPreviewAfterLen"), &afterLen, 1.0);

    auto playOptions = ProjectAudioIO::GetDefaultOptions(project);

    TransportUtilities::PlayPlayRegionAndWait(
        context, SelectedRegion(t1, t1 + afterLen),
        playOptions, PlayMode::oneSecondPlay);
}

void OnPlayBeforeAndAfterSelectionStart
    (const CommandContext& context)
{
    auto& project = context.project;

    if (!MakeReadyToPlay(project)) {
        return;
    }

    auto& viewInfo = ViewInfo::Get(project);
    const auto& selectedRegion = viewInfo.selectedRegion;

    double t0 = selectedRegion.t0();
    double t1 = selectedRegion.t1();
    double beforeLen;
    gPrefs->Read(wxT("/AudioIO/CutPreviewBeforeLen"), &beforeLen, 2.0);
    double afterLen;
    gPrefs->Read(wxT("/AudioIO/CutPreviewAfterLen"), &afterLen, 1.0);

    auto playOptions = ProjectAudioIO::GetDefaultOptions(project);

    if (t1 - t0 > 0.0 && t1 - t0 < afterLen) {
        TransportUtilities::PlayPlayRegionAndWait(
            context, SelectedRegion(t0 - beforeLen, t1),
            playOptions, PlayMode::oneSecondPlay);
    } else {
        TransportUtilities::PlayPlayRegionAndWait(
            context, SelectedRegion(t0 - beforeLen, t0 + afterLen),
            playOptions, PlayMode::oneSecondPlay);
    }
}

void OnPlayBeforeAndAfterSelectionEnd
    (const CommandContext& context)
{
    auto& project = context.project;

    if (!MakeReadyToPlay(project)) {
        return;
    }

    auto& viewInfo = ViewInfo::Get(project);
    const auto& selectedRegion = viewInfo.selectedRegion;

    double t0 = selectedRegion.t0();
    double t1 = selectedRegion.t1();
    double beforeLen;
    gPrefs->Read(wxT("/AudioIO/CutPreviewBeforeLen"), &beforeLen, 2.0);
    double afterLen;
    gPrefs->Read(wxT("/AudioIO/CutPreviewAfterLen"), &afterLen, 1.0);

    auto playOptions = ProjectAudioIO::GetDefaultOptions(project);

    if (t1 - t0 > 0.0 && t1 - t0 < beforeLen) {
        TransportUtilities::PlayPlayRegionAndWait(
            context, SelectedRegion(t0, t1 + afterLen),
            playOptions, PlayMode::oneSecondPlay);
    } else {
        TransportUtilities::PlayPlayRegionAndWait(
            context, SelectedRegion(t1 - beforeLen, t1 + afterLen),
            playOptions, PlayMode::oneSecondPlay);
    }
}

void OnPlayCutPreview(const CommandContext& context)
{
    auto& project = context.project;

    if (!MakeReadyToPlay(project)) {
        return;
    }

    // Play with cut preview
    TransportUtilities::PlayCurrentRegionAndWait(context, false, true);
}

void OnMoveToPrevLabel(const CommandContext& context)
{
    auto& project = context.project;
    DoMoveToLabel(project, false);
}

void OnMoveToNextLabel(const CommandContext& context)
{
    auto& project = context.project;
    DoMoveToLabel(project, true);
}

#if 0
// Legacy handlers, not used as of version 2.3.0
void OnStopSelect(const CommandContext& context)
{
    auto& project = context.project;
    auto& history = ProjectHistory::Get(project);
    auto& viewInfo = project.GetViewInfo();
    auto& selectedRegion = viewInfo.selectedRegion;

    auto gAudioIO = AudioIOBase::Get();
    if (gAudioIO->IsStreamActive()) {
        selectedRegion.setT0(gAudioIO->GetStreamTime(), false);
        ProjectAudioManager::Get(project).Stop();
        history.ModifyState(false);         // without bWantsAutoSave
    }
}

#endif

// Menu definitions

// Under /MenuBar
using namespace MenuRegistry;
auto TransportMenu()
{
    static const auto CanStopFlags
        =AudioIONotBusyFlag() | CanStopAudioStreamFlag();
    static auto menu = std::shared_ptr{
        /* i18n-hint: 'Transport' is the name given to the set of controls that
           play, record, pause etc. */
        Menu(wxT("Transport"), XXO("Tra&nsport"),
             Section("Basic",
                     Menu(wxT("Play"), XXO("Pl&aying"),
                          /* i18n-hint: (verb) Start or Stop audio playback*/
                          Command(wxT("DefaultPlayStop"), XXO("Pl&ay/Stop"), OnPlayDefaultOrStop,
                                  CanStopAudioStreamFlag(), wxT("Space")),
                          Command(wxT("PlayStopSelect"), XXO("Play/Stop and &Set Cursor"),
                                  OnPlayStopSelect, CanStopAudioStreamFlag(), wxT("X")),
                          Command(wxT("OncePlayStop"), XXO("Play &Once/Stop"), OnPlayOnceOrStop,
                                  CanStopAudioStreamFlag(), wxT("Shift+Space")),
                          Command(wxT("Pause"), XXO("&Pause"), OnPause,
                                  CanStopAudioStreamFlag(), wxT("P"))
                          ),

                     Menu(wxT("Record"), XXO("&Recording"),
                          /* i18n-hint: (verb)*/
                          Command(wxT("Record1stChoice"), XXO("&Record"), OnRecord,
                                  CanStopFlags, wxT("R")),

                          // The OnRecord2ndChoice function is: if normal record records beside,
                          // it records below, if normal record records below, it records beside.
                          // TODO: Do 'the right thing' with other options like TimerRecord.
                          // Delayed evaluation in case gPrefs is not yet defined
                          [](const AudacityProject&)
        {
            return Command(wxT("Record2ndChoice"),
                           // Our first choice is bound to R (by default)
                           // and gets the prime position.
                           // We supply the name for the 'other one' here.
                           // It should be bound to Shift+R
                           (gPrefs->ReadBool("/GUI/PreferNewTrackRecord", false)
                            ? XXO("&Append Record") : XXO("Record &New Track")),
                           OnRecord2ndChoice, CanStopFlags,
                           wxT("Shift+R")
                           );
        },

                          Command(wxT("PunchAndRoll"), XXO("Punch and Rol&l Record"),
                                  OnPunchAndRoll,
                                  WaveTracksExistFlag() | AudioIONotBusyFlag(), wxT("Shift+D")),

                          // JKC: I decided to duplicate this between play and record,
                          // rather than put it at the top level.
                          // CommandManger::AddItem can now cope with simple duplicated items.
                          // PRL:  caution, this is a duplicated command name!
                          Command(wxT("Pause"), XXO("&Pause"), OnPause,
                                  CanStopAudioStreamFlag(), wxT("P"))
                          )
                     ),

             Section("Other",
                     Section("",
                             Menu(wxT("PlayRegion"), XXO("&Looping"),
                                  Command(wxT("TogglePlayRegion"), LoopToggleText,
                                          OnTogglePlayRegion, AlwaysEnabledFlag,
                                          Options(L"L").CheckTest([](const AudacityProject& project){
            return IsLoopingEnabled(project);
        })),
                                  Command(wxT("ClearPlayRegion"), XXO("&Clear Loop"),
                                          OnClearPlayRegion, AlwaysEnabledFlag, L"Shift+Alt+L"),
                                  Command(wxT("SetPlayRegionToSelection"),
                                          XXO("&Set Loop to Selection"),
                                          OnSetPlayRegionToSelection, AlwaysEnabledFlag,
                                          L"Shift+L"),
                                  Command(wxT("SetPlayRegionIn"),
                                          SetLoopInTitle,
                                          OnSetPlayRegionIn, AlwaysEnabledFlag),
                                  Command(wxT("SetPlayRegionOut"),
                                          SetLoopOutTitle,
                                          OnSetPlayRegionOut, AlwaysEnabledFlag)
                                  )
                             ),

                     Command(wxT("RescanDevices"), XXO("R&escan Audio Devices"),
                             OnRescanDevices, AudioIONotBusyFlag() | CanStopAudioStreamFlag()),

                     Menu(wxT("Options"), XXO("Transport &Options"),
                          Section("Part1",
                                  // Sound Activated recording options
                                  Command(wxT("SoundActivationLevel"),
                                          XXO("Set sound activation le&vel..."), OnSoundActivated,
                                          AudioIONotBusyFlag() | CanStopAudioStreamFlag()),
                                  Command(wxT("SoundActivation"),
                                          XXO("Enable sound a&ctivated recording"),
                                          OnToggleSoundActivated,
                                          AudioIONotBusyFlag() | CanStopAudioStreamFlag(),
                                          Options {}.CheckTest(SoundActivatedRecord))
                                  ),

                          Section("Part2",
                                  Command(wxT("Overdub"), XXO("Hear &other tracks during recording"),
                                          OnTogglePlayRecording,
                                          AudioIONotBusyFlag() | CanStopAudioStreamFlag(),
                                          Options {}.CheckTest(wxT("/AudioIO/Duplex"), true)),
                                  Command(wxT("SWPlaythrough"), XXO("Enable audible input &monitoring"),
                                          OnToggleSWPlaythrough,
                                          AudioIONotBusyFlag() | CanStopAudioStreamFlag(),
                                          Options {}.CheckTest(wxT("/AudioIO/SWPlaythrough"), false))
                                  )
                          )
                     )
             ) };
    return menu;
}

AttachedItem sAttachment1{ Indirect(TransportMenu()) };

auto ExtraTransportMenu()
{
    static auto menu = std::shared_ptr{
        Menu(wxT("Transport"), XXO("T&ransport"),
             // PlayStop is already in the menus.
             /* i18n-hint: (verb) Start playing audio*/
             Command(wxT("Play"), XXO("Pl&ay Once"), OnPlayOnceOrStop,
                     WaveTracksExistFlag() | AudioIONotBusyFlag()),
             /* i18n-hint: (verb) Stop playing audio*/
             Command(wxT("Stop"), XXO("Sto&p"), OnStop,
                     AudioIOBusyFlag() | CanStopAudioStreamFlag()),
             Command(wxT("PlayOneSec"), XXO("Play &One Second"), OnPlayOneSecond,
                     CaptureNotBusyFlag(), wxT("1")),
             Command(wxT("PlayToSelection"), XXO("Play to &Selection"),
                     OnPlayToSelection,
                     CaptureNotBusyFlag(), wxT("B")),
             Command(wxT("PlayBeforeSelectionStart"),
                     XXO("Play &Before Selection Start"), OnPlayBeforeSelectionStart,
                     CaptureNotBusyFlag(), wxT("Shift+F5")),
             Command(wxT("PlayAfterSelectionStart"),
                     XXO("Play Af&ter Selection Start"), OnPlayAfterSelectionStart,
                     CaptureNotBusyFlag(), wxT("Shift+F6")),
             Command(wxT("PlayBeforeSelectionEnd"),
                     XXO("Play Be&fore Selection End"), OnPlayBeforeSelectionEnd,
                     CaptureNotBusyFlag(), wxT("Shift+F7")),
             Command(wxT("PlayAfterSelectionEnd"),
                     XXO("Play Aft&er Selection End"), OnPlayAfterSelectionEnd,
                     CaptureNotBusyFlag(), wxT("Shift+F8")),
             Command(wxT("PlayBeforeAndAfterSelectionStart"),
                     XXO("Play Before a&nd After Selection Start"),
                     OnPlayBeforeAndAfterSelectionStart, CaptureNotBusyFlag(),
                     wxT("Ctrl+Shift+F5")),
             Command(wxT("PlayBeforeAndAfterSelectionEnd"),
                     XXO("Play Before an&d After Selection End"),
                     OnPlayBeforeAndAfterSelectionEnd, CaptureNotBusyFlag(),
                     wxT("Ctrl+Shift+F7")),
             Command(wxT("PlayCutPreview"), XXO("Play C&ut Preview"),
                     OnPlayCutPreview,
                     CaptureNotBusyFlag(), wxT("C"))
             ) };
    return menu;
}

AttachedItem sAttachment2{ Indirect(ExtraTransportMenu()),
                           wxT("Optional/Extra/Part1")
};

auto ExtraSelectionItems()
{
    static auto items = std::shared_ptr{
        Items(wxT("MoveToLabel"),
              Command(wxT("MoveToPrevLabel"), XXO("Move to Pre&vious Label"),
                      OnMoveToPrevLabel,
                      CaptureNotBusyFlag() | TrackPanelHasFocus(), wxT("Alt+Left")),
              Command(wxT("MoveToNextLabel"), XXO("Move to Ne&xt Label"),
                      OnMoveToNextLabel,
                      CaptureNotBusyFlag() | TrackPanelHasFocus(), wxT("Alt+Right"))
              ) };
    return items;
}

AttachedItem sAttachment4{ Indirect(ExtraSelectionItems()),
                           { wxT("Optional/Extra/Part1/Select"), { OrderingHint::End, {} } }
};
}
